#!/usr/bin/env bash

set -eo pipefail

PROJECT_NAME="crowd"
DOCKER_NETWORK_SUBNET="${CROWD_NETWORK_SUBNET:-10.90.0.0/24}"
DOCKER_NETWORK_GATEWAY="${CROWD_NETWORK_GATEWAY:-10.90.0.1}"
DOCKET_TEST_NETWORK_SUBNET="${CROWD_TEST_NETWORK_SUBNET:-10.91.0.0/24}"
DOCKER_TEST_NETWORK_GATEWAY="${CROWD_TEST_NETWORK_GATEWAY:-10.91.0.1}"

CLI_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
source $CLI_HOME/utils
source $CLI_HOME/scaffold/kafka-connect/build-docker-image.sh

CONTAINERS=$(ls -p $CLI_HOME/services | grep -v / | sed 's/\.[^.]*$//')
BUILDERS=$(ls -p $CLI_HOME/builders | grep -v / | sed 's/\.[^.]*$//')

DOCKER_COMPOSE_PROFILE=''
DOCKER_COMPOSE_SCAFFOLD_FILES="-f $CLI_HOME/scaffold.yaml"

TB_HOST="http://localhost:7181"

if [[ -n "${WITH_NGINX}" ]]; then
    DOCKER_COMPOSE_PROFILE='--profile nginx'
elif [[ "$WITH_INSIGHTS" == "1" ]]; then
    DOCKER_COMPOSE_PROFILE='--profile insights'
    DOCKER_COMPOSE_SCAFFOLD_FILES="-f $CLI_HOME/scaffold.yaml -f $CLI_HOME/scaffold.insights.yaml"
fi

_DC="docker compose"

arch=$(uname -m)

if [ "$arch" == 'arm64' ]; then
    DOCKER_PLATFORM_FLAGS="--platform=linux/arm64/v8"
else
    DOCKER_PLATFORM_FLAGS="--platform=linux/amd64"
fi

TINYBIRD_DIR="$CLI_HOME/../services/libs/tinybird"
TINYBIRD_VENV_PATH="$TINYBIRD_DIR/.venv"

function prepare_dev_env() {
    if [[ ! -f "$CLI_HOME/../backend/.env.override.local" ]]; then
        echo "# Here you can put environment variables that you would like to override when using local environment" >"$CLI_HOME/../backend/.env.override.local"
    fi
    if [[ ! -f "$CLI_HOME/../backend/.env.override.composed" ]]; then
        echo "# Here you can put environment variables that you would like to override when using local environment" >"$CLI_HOME/../backend/.env.override.composed"
    fi

    if [[ ! -f "$CLI_HOME/../frontend/.env.override.local" ]]; then
        echo "# Here you can put environment variables that you would like to override when using local environment" >"$CLI_HOME/../frontend/.env.override.local"
    fi

    if [[ ! -f "$CLI_HOME/../frontend/.env.override.composed" ]]; then
        echo "# Here you can put environment variables that you would like to override when using local environment" >"$CLI_HOME/../frontend/.env.override.composed"
    fi

    set +eo pipefail

    $_DC >>/dev/null 2>&1

    if [[ ! $? -eq 0 ]]; then
        _DC="docker-compose"
        $_DC >>/dev/null 2>&1

        if [[ ! $? -eq 0 ]]; then
            error "Docker compose not detected!"
            exit 1
        fi
    fi

    set -eo pipefail

    mkdir -p $CLI_HOME/scaffold/kafka-connect/tmp/kafka-connect-http
}

prepare_dev_env

function prepare_service_string() {
    if [ $1 == "HELP" ]; then
        delimiter1=" | "
        delimiter2=" => | "
    fi
    if [ $1 == "LIST" ]; then
        delimiter1="\|"
        delimiter2="\|"
    fi

    for cmd in $SERVICE_CMD; do string+="$cmd$delimiter1"; done
    for container in $2; do string+="$container$delimiter2"; done

    echo ${string%??}
}

HELP_STRING=$(prepare_service_string HELP "$CONTAINERS")
LIST_STRING=$(prepare_service_string LIST "$CONTAINERS")
HELP_BUILD_STRING=$(prepare_service_string HELP "$BUILDERS")

function multiselect {
    # little helpers for terminal print control and key input
    ESC=$(printf "\033")
    cursor_blink_on() { printf "$ESC[?25h"; }
    cursor_blink_off() { printf "$ESC[?25l"; }
    cursor_to() { printf "$ESC[$1;${2:-1}H"; }
    print_inactive() { printf "$2   $1 "; }
    print_active() { printf "$2  $ESC[7m $1 $ESC[27m"; }
    get_cursor_row() {
        IFS=';' read -sdR -p $'\E[6n' ROW COL
        echo ${ROW#*[}
    }

    local return_value=$1
    local options=("${!2}")
    local defaults=()

    local selected=()
    for ((i = 0; i < ${#options[@]}; i++)); do
        if [[ ${defaults[i]} == "true" ]]; then
            selected+=("true")
        else
            selected+=("false")
        fi
        printf "\n"
    done

    local lastrow=$(get_cursor_row)
    local startrow=$(($lastrow - ${#options[@]}))

    trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
    cursor_blink_off

    key_input() {
        local key
        IFS= read -rsn1 key
        if [[ -z $key ]]; then
            echo enter
            return
        fi # Handle Enter key (when key is a newline)

        case "$key" in
        $'\x20') echo space ;; # Space key
        k) echo up ;;          # 'k' for up
        j) echo down ;;        # 'j' for down
        $'\x1b')               # Handle arrow keys
            read -rsn2 key
            if [[ $key == "[A" ]]; then echo up; fi   # Up arrow
            if [[ $key == "[B" ]]; then echo down; fi # Down arrow
            ;;
        *) echo "unknown" ;; # Handle other keys
        esac
    }

    toggle_option() {
        local option=$1
        if [[ ${selected[option]} == true ]]; then
            selected[option]=false
        else
            selected[option]=true
        fi
    }

    print_options() {
        local idx=0
        for option in "${options[@]}"; do
            local prefix="[ ]"
            if [[ ${selected[idx]} == true ]]; then
                prefix="[\e[38;5;46m✔\e[0m]"
            fi

            cursor_to $(($startrow + $idx))
            if [ $idx -eq $1 ]; then
                print_active "$option" "$prefix"
            else
                print_inactive "$option" "$prefix"
            fi
            ((idx++))
        done
    }

    local active=0
    while true; do
        print_options $active

        case $(key_input) in
        space) toggle_option $active ;;
        enter)
            print_options -1
            break
            ;;
        up)
            ((active--))
            if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi
            ;;
        down)
            ((active++))
            if [ $active -ge ${#options[@]} ]; then active=0; fi
            ;;
        esac
    done

    cursor_to $lastrow
    printf "\n"
    cursor_blink_on

    # Construct the result array based on selections
    local result=()
    for i in "${!selected[@]}"; do
        if [[ ${selected[i]} == true ]]; then
            result+=(${options[$i]})
        fi
    done

    # Return the result
    eval $return_value='("${result[@]}")'
}

SELECTED_SERVICES=()

function select_services() {
    # Get all services
    SERVICES=()
    while IFS= read -r file; do
        filename=$(basename "$file" .yaml)
        SERVICES+=("$filename")
    done < <(find "$CLI_HOME/services" -name '*.yaml')

    # Show multiselect and store results in SELECTED_SERVICES
    multiselect SELECTED_SERVICES SERVICES[@]

    for service in "${SELECTED_SERVICES[@]}"; do
        selected_services+="$service, "
    done

    selected_services=${selected_services%, }

    echo "Selected services: $selected_services"
}

function deploy_staging() {
    REPOSITORY="CrowdDotDev/crowd.dev"
    WORKFLOW_FILE="lf-staging-deploy.yaml"
    CURRENT_BRANCH=$(git branch --show-current)
    SERVICES="$(
        IFS=" "
        echo "${SELECTED_SERVICES[*]}"
    )"

    gh workflow run $WORKFLOW_FILE --repo $REPOSITORY --ref $CURRENT_BRANCH -f services="$SERVICES"
}

function deploy_production() {
    REPOSITORY="CrowdDotDev/crowd.dev"
    WORKFLOW_FILE="lf-production-deploy.yaml"
    CURRENT_BRANCH=$(git branch --show-current)
    SERVICES="$(
        IFS=" "
        echo "${SELECTED_SERVICES[*]}"
    )"

    gh workflow run $WORKFLOW_FILE --repo $REPOSITORY --ref $CURRENT_BRANCH -f services="$SERVICES"
}

function reset_selected_services() {
    for SERVICE in "${SELECTED_SERVICES[@]}"; do
        echo "Restarting service $SERVICE."
        kill_containers $SERVICE && start_service $SERVICE
    done
}

function build() {
    HELP="${RESET}\nUsage:\n ./cli build [ $HELP_BUILD_STRING ]\n"
    [[ -z "$1" ]] && say "$HELP" && exit 1

    if [[ $BUILDERS =~ (^|[[:space:]])"$1"($|[[:space:]]) ]]; then
        build_and_publish "$@"
    else
        error "Invalid command '$1'" && say "$HELP"
        exit 1
    fi
}

function create_migration() {
    MIG_NAME="$1"
    MIG_VERSION=$(date +%s)
    UP_MIG_FILE="${CLI_HOME}/../backend/src/database/migrations/V${MIG_VERSION}__${MIG_NAME}.sql"
    DOWN_MIG_FILE="${CLI_HOME}/../backend/src/database/migrations/U${MIG_VERSION}__${MIG_NAME}.sql"
    touch $UP_MIG_FILE
    touch $DOWN_MIG_FILE
    yell "Created ${MIG_FILE}"
}

function create_product_migration() {
    MIG_NAME="$1"
    MIG_VERSION=$(date +%s)
    UP_MIG_FILE="${CLI_HOME}/../backend/src/product/migrations/V${MIG_VERSION}__${MIG_NAME}.sql"
    DOWN_MIG_FILE="${CLI_HOME}/../backend/src/product/migrations/U${MIG_VERSION}__${MIG_NAME}.sql"
    touch $UP_MIG_FILE
    touch $DOWN_MIG_FILE
    yell "Created ${MIG_FILE}"
}

function build_and_publish() {
    VERSION="$2"

    if [[ -z "${VERSION}" ]]; then
        COMMIT_HASH=$(git rev-parse --short HEAD)
        TS_VERSION=$(date +%s)
        VERSION="$TS_VERSION.$COMMIT_HASH"
    fi

    source $CLI_HOME/builders/$1.env

    if [[ ${BUILD} ]]; then
        say "Building $REPO version $VERSION with dockerfile '$DOCKERFILE' and context '$CONTEXT' and pushing it to $REPO:$VERSION"
        docker build --platform linux/amd64 --tag "$REPO:$VERSION" -f "$DOCKERFILE" "$CONTEXT"
    fi

    if [[ ${PUSH} ]]; then
        say "Pushing image $REPO version $VERSION to $REPO:$VERSION"
        docker push "$REPO:$VERSION"
    fi
}

function db_backup() {
    [[ -z "$1" ]] && error "Dump name has to be provided as first parameter!" && exit 1

    mkdir -p $CLI_HOME/db_dumps

    say "Cloning local database to $CLI_HOME/db_dumps/$1.dump!"
    docker exec -t ${PROJECT_NAME}_db_1 bash -c "PGPASSWORD=example pg_dump -F c -d crowd-web -U postgres > /$1.dump"
    docker cp ${PROJECT_NAME}_db_1:/$1.dump $CLI_HOME/db_dumps/$1.dump

    say "All done!"
}

function restore_db_backup() {
    [[ -z "$1" ]] && error "Dump name has to be provided as first parameter!" && exit 1

    say "First we need to clean up the scaffold!"
    scaffold_destroy
    up_scaffold

    say "Sleeping for 5 seconds for the database container to start up!"
    sleep 5

    say "Restoring dump from $CLI_HOME/db_dumps/$1.dump"
    docker cp $CLI_HOME/db_dumps/$1.dump ${PROJECT_NAME}_db_1:/backup.dump
    # docker exec -t ${PROJECT_NAME}_db_1 bash -c "PGPASSWORD=example dropdb -U postgres crowd-web && PGPASSWORD=example createdb -U postgres crowd-web"
    set +eo pipefail
    docker exec -t ${PROJECT_NAME}_db_1 bash -c "PGPASSWORD=example pg_restore --clean -U postgres -d crowd-web backup.dump && rm -f backup.dump"
    set -eo pipefail
    post_up_scaffold
    say "All done!"
}

function scaffold() {
    HELP="${RESET}\nUsage:\n ./cli scaffold [ up | down | destroy | reset ]\n"

    [[ -z "$1" ]] && say "$HELP" && exit 1

    while test $# -gt 0; do
        case "$1" in
        up)
            up_scaffold
            post_up_scaffold
            exit
            ;;
        down)
            down_scaffold
            exit
            ;;
        destroy)
            scaffold_destroy
            exit
            ;;
        reset)
            scaffold_reset
            exit
            ;;
        create-migration)
            create_migration $2
            exit
            ;;
        create-product-migration)
            create_product_migration $2
            exit
            ;;
        migrate-up)
            migrate_local
            exit
            ;;
        up-test)
            up_test_scaffold
            exit
            ;;
        *)
            error "Invalid command '$1'" && say "$HELP"
            exit 1
            ;;
        esac
        shift
    done
}

function service() {
    HELP="${RESET}\nUsage:\n ./cli service [ $HELP_STRING ]\n"
    [[ -z "$1" ]] && say "$HELP" && exit 1

    while test $# -gt 0; do
        case "$1" in
        list)
            docker container ls | grep $LIST_STRING
            exit
            ;;
        up-all)
            start_all_containers
            exit
            ;;
        *)
            if [[ $CONTAINERS =~ (^|[[:space:]])"$1"($|[[:space:]]) ]]; then
                service_manipulator $1 $2 $3
                exit
            else
                error "Invalid command '$1'" && say "$HELP"
                exit 1
            fi
            ;;
        esac
        shift
    done
}

function service_manipulator() {
    HELP="${RESET}\nUsage:\n ./cli service $1 [ up | down | restart | status | logs | id ]\n"

    [[ -z "$2" ]] && say "$HELP" && exit 1

    while test $# -gt 0; do
        case "$2" in
        up)
            start_service "$1"
            exit
            ;;
        down)
            kill_containers "$1"
            exit
            ;;
        restart)
            kill_containers "$1" && start_service "$1"
            exit
            ;;
        status)
            print_container_status "$1"
            exit
            ;;
        logs)
            get_logs "$1"
            exit
            ;;
        id)
            get_container_id "$1"
            exit
            ;;
        *)
            error "Invalid command '$2'" && say "$HELP"
            exit 1
            ;;
        esac
        shift
    done
}

function start_service() {
    export USER_ID=$(id -u)
    export GROUP_ID=$(id -g)

    if [[ ${DEV} ]]; then
        $_DC --compatibility -p $PROJECT_NAME -f "$CLI_HOME/services/${1}.yaml" up --build -d ${1}-dev
    else
        $_DC --compatibility -p $PROJECT_NAME -f "$CLI_HOME/services/${1}.yaml" up --build -d ${1}
    fi
}

function kill_containers() {
    $_DC --compatibility -p $PROJECT_NAME -f "$CLI_HOME/services/${1}.yaml" rm -fs ${1} ${1}-dev
}

function get_logs() {
    if [[ ${DEV} ]]; then
        docker container logs -f $(get_container_id "$1-dev")
    else
        docker container logs -f $(get_container_id "$1")
    fi
}

function print_container_status() {
    CONTAINER_STATUS=$(check_container_status $1)

    if [[ ${CONTAINER_STATUS} ]]; then
        check_container_status "$1"
    else
        error "Down."
        exit 1
    fi
}

function get_container_id() {
    docker container ls -a | grep "${PROJECT_NAME}_${1}_" | tr " " "\n" | head -n 1
}

function check_container_status() {
    docker container ls -a | grep "${PROJECT_NAME}_${1}_"
}

# <network-name> <subnet> <gateway>
function scaffold_set_up_network() {
    NETWORK_NAME="$1"
    NETWORK_SUBNET="$2"
    NETWORK_GATEWAY="$3"

    set +e pipefail
    NETWORK_ID=$(docker network ls | grep -F -e "$NETWORK_NAME " | tr " " "\n" | head -n 1)
    set -e pipefail

    if [[ ${NETWORK_ID} ]]; then
        say "The $NETWORK_NAME network is up and running."
    else
        docker network create -d bridge --subnet "$NETWORK_SUBNET" --gateway "$NETWORK_GATEWAY" "$NETWORK_NAME"
    fi
}

function migrate_env() {
    if [[ -z "$CROWD_DB_WRITE_HOST" ]]; then
        error "No CROWD_DB_WRITE_HOST env variable set!"
        exit 1
    fi

    say "Building flyway migration image..."
    docker build $DOCKER_PLATFORM_FLAGS -t crowd_flyway -f $CLI_HOME/../backend/src/database/Dockerfile.flyway $CLI_HOME/../backend/src/database --load
    say "Applying PostgreSQL migrations to $CROWD_DB_WRITE_HOST!"
    docker run --rm \
        -e PGHOST=$CROWD_DB_WRITE_HOST \
        -e PGPORT=$CROWD_DB_PORT \
        -e PGUSER=$CROWD_DB_USERNAME \
        -e PGPASSWORD=$CROWD_DB_PASSWORD \
        -e PGDATABASE=$CROWD_DB_DATABASE \
        crowd_flyway
}

function wait_for_tinybird() {
    say "Waiting for Tinybird to get ready.."

    MAX_RETRIES=10
    RETRY_DELAY=10
    attempt=1

    # First, wait for tokens endpoint
    while [ $attempt -le $MAX_RETRIES ]; do
        CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN=$(curl -s "$TB_HOST/tokens" | jq -r '.workspace_admin_token')

        if [ -n "$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN" ]; then
            say "Tinybird tokens endpoint is ready!"
            break
        fi

        echo "Tokens endpoint not ready yet. Retrying in $RETRY_DELAY seconds..."
        sleep $RETRY_DELAY
        attempt=$((attempt + 1))
    done

    if [ $attempt -gt $MAX_RETRIES ]; then
        error "Tinybird was not ready after $MAX_RETRIES attempts. Exiting."
        return 1
    fi
    
    # Now verify workspace API is ready by testing the actual endpoint
    say "Verifying Tinybird workspace API is ready..."
    attempt=1
    MAX_WORKSPACE_RETRIES=10
    
    while [ $attempt -le $MAX_WORKSPACE_RETRIES ]; do
        HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$TB_HOST/v0/workspace?token=$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN&cli_version=5.21.1")
        
        if [ "$HTTP_CODE" = "200" ]; then
            say "Tinybird workspace API is ready!"
            break
        fi
        
        echo "Workspace API returned $HTTP_CODE. Retrying in 10 seconds... (attempt $attempt/$MAX_WORKSPACE_RETRIES)"
        sleep 10
        attempt=$((attempt + 1))
    done
    
    if [ $attempt -gt $MAX_WORKSPACE_RETRIES ]; then
        error "Tinybird workspace API was not ready after $MAX_WORKSPACE_RETRIES attempts. Proceeding anyway..."
    fi
}

function set_tinybird_token_in_env() {
    ENV_FILE="$CLI_HOME/../backend/.env.override.local"
    
    say "Setting CROWD_TINYBIRD_ACTIVITIES_TOKEN in $ENV_FILE"
    
    if grep -q "^CROWD_TINYBIRD_ACTIVITIES_TOKEN=" "$ENV_FILE"; then
        if [[ "$OSTYPE" == "darwin"* ]]; then
            sed -i '' "s|^CROWD_TINYBIRD_ACTIVITIES_TOKEN=.*|CROWD_TINYBIRD_ACTIVITIES_TOKEN=$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN|" "$ENV_FILE"
        else
            sed -i "s|^CROWD_TINYBIRD_ACTIVITIES_TOKEN=.*|CROWD_TINYBIRD_ACTIVITIES_TOKEN=$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN|" "$ENV_FILE"
        fi
        say "Updated CROWD_TINYBIRD_ACTIVITIES_TOKEN in .env.override.local"
    else
        echo "CROWD_TINYBIRD_ACTIVITIES_TOKEN=$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN" >> "$ENV_FILE"
        say "Added CROWD_TINYBIRD_ACTIVITIES_TOKEN to .env.override.local"
    fi
}

function migrate_tinybird_local() {
    set +eo pipefail

    wait_for_tinybird
    set_tinybird_token_in_env

    activate_tinybird_cli

    say "Tinybird CLI version:"
    tb --version
    
    say "Authenticating to tinybird local ($TB_HOST) using $CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN"
    
    # Change to tinybird directory and run auth
    pushd "$TINYBIRD_DIR" > /dev/null
    
    # Retry logic for tb auth (should rarely be needed now)
    MAX_RETRIES=2
    RETRY_COUNT=0
    
    while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
        if tb auth --host "$TB_HOST" --token "$CROWD_TINYBIRD_WORKSPACE_ADMIN_TOKEN"; then
            say "Tinybird authentication successful!"
            break
        else
            RETRY_COUNT=$((RETRY_COUNT + 1))
            if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
                say "Authentication failed, retrying in 5 seconds... (attempt $((RETRY_COUNT + 1))/$MAX_RETRIES)"
                sleep 5
            else
                error "Failed to authenticate to Tinybird after $MAX_RETRIES attempts"
            fi
        fi
    done

    tb push datasources/activityRelations_deduplicated_ds.datasource
    tb push datasources/activities_deduplicated_ds.datasource
    tb push datasources/activities.datasource
    tb push datasources/activityRelations.datasource

    tb push pipes/activities_relations_filtered.pipe
    tb push pipes/activities_daily_counts.pipe

    tb pipe publish activities_relations_filtered
    tb pipe publish activities_daily_counts
    # ignore errors because pipe gets created but then some weird thing happens on local tinybird and it triggers an error
    tb push pipes/activities_deduplicated_copy_pipe.pipe > /dev/null 2>&1 || true
    tb push pipes/activityRelations_deduplicated_copy_pipe.pipe > /dev/null 2>&1 || true
    
    popd > /dev/null
    
    deactivate_tinybird_cli

    set -eo pipefail
}

function migrate_postgres_local() {
    say "Building crowd flyway migration image..."
    docker build $DOCKER_PLATFORM_FLAGS -t crowd_flyway -f $CLI_HOME/../backend/src/database/Dockerfile.flyway $CLI_HOME/../backend/src/database --load
    say "Applying PostgreSQL migrations!"
    docker run --rm --network "${PROJECT_NAME}-bridge" \
        -e PGHOST=db \
        -e PGPORT=5432 \
        -e PGUSER=postgres \
        -e PGPASSWORD=example \
        -e PGDATABASE=crowd-web \
        crowd_flyway
}

function migrate_productdb_local() {
    say "Building product flyway migration image..."
    docker build $DOCKER_PLATFORM_FLAGS -t product_flyway -f $CLI_HOME/../backend/src/product/Dockerfile.flyway $CLI_HOME/../backend/src/product
    say "Applying product database migrations!"
    docker run --rm --network "${PROJECT_NAME}-bridge" \
        -e PGHOST=product \
        -e PGPORT=5432 \
        -e PGUSER=postgres \
        -e PGPASSWORD=example \
        -e PGDATABASE=product-db \
        product_flyway
}

function migrate_local() {
    migrate_postgres_local
    migrate_productdb_local
    migrate_tinybird_local
}

function up_test_scaffold() {
    scaffold_set_up_network "${PROJECT_NAME}-bridge-test" $DOCKET_TEST_NETWORK_SUBNET $DOCKER_TEST_NETWORK_GATEWAY
    $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/../backend/docker-compose.test.yaml down
    $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/../backend/docker-compose.test.yaml up -d
    migrate_test
}

function migrate_test() {
    say "Building flyway migration image..."
    docker build -t crowd_flyway -f $CLI_HOME/../backend/src/database/Dockerfile.flyway $CLI_HOME/../backend/src/database

    say "Applying database migrations!"
    docker run --rm --network "${PROJECT_NAME}-bridge-test" \
        -e PGHOST=db-test \
        -e PGPORT=5432 \
        -e PGUSER=postgres \
        -e PGPASSWORD=example \
        -e PGDATABASE=crowd-web \
        crowd_flyway
}

function source_edition() {
    __CROWD_EDITION=$(source $CLI_HOME/../backend/.env.dist.local && source $CLI_HOME/../backend/.env.override.local && echo $CROWD_EDITION)
    nl
    say "Crowd edition detected: $__CROWD_EDITION"
}

function install_libs() {
    (cd $CLI_HOME/.. && pnpm i --frozen-lockfile)
}

function install_tinybird_cli() {    
    say "Setting up Tinybird CLI..."
    
    if [[ -d "$TINYBIRD_VENV_PATH" ]]; then
        say "Virtual environment already exists at $TINYBIRD_VENV_PATH, using it..."
    else
        say "Creating Python virtual environment at $TINYBIRD_VENV_PATH..."
        python3 -m venv "$TINYBIRD_VENV_PATH"
    fi
    
    say "Installing Tinybird CLI dependencies..."
    activate_tinybird_cli
    pip install -r "$TINYBIRD_DIR/requirements.txt"
    tb --version
    deactivate_tinybird_cli
    
    say "Tinybird CLI setup complete!"
}

function  activate_tinybird_cli() {
    source "$TINYBIRD_VENV_PATH/bin/activate"
}

function deactivate_tinybird_cli() {
    deactivate
}


function up_scaffold() {
    scaffold_set_up_network "$PROJECT_NAME-bridge" $DOCKER_NETWORK_SUBNET $DOCKER_NETWORK_GATEWAY

    download_kafka_connect_http "$CLI_HOME/scaffold/kafka-connect"
    install_tinybird_cli
    
    # Start all services - kafka-connect will wait for tinybird via configure-tinybird-sink.sh
    $_DC --compatibility -p $PROJECT_NAME $DOCKER_COMPOSE_SCAFFOLD_FILES $DOCKER_COMPOSE_PROFILE up -d --build --no-recreate

}

function post_up_scaffold() {
    migrate_local
    bash $CLI_HOME/nango-integrations.sh
}

function down_scaffold() {
    $_DC --compatibility -p $PROJECT_NAME $DOCKER_COMPOSE_SCAFFOLD_FILES $DOCKER_COMPOSE_PROFILE down
}

function scaffold_destroy() {
    say "\nWill delete all local crowd state data (docker volumes) if you have any. Are you sure?"
    select reset_system_condition in "Yes" "No"; do
        case $reset_system_condition in
        'Yes')
            scaffold_destroy_confirmed
            break
            ;;
        'No')
            yell "Canceled!"
            break
            ;;
        esac
    done
}

function scaffold_reset() {
    scaffold_destroy
    up_scaffold
    post_up_scaffold
}

function kill_all_containers() {
    for i in $CONTAINERS; do
        say "Killing service $i."

        if [[ $(check_container_status ${i}) ]]; then
            docker rm -f $(get_container_id ${i})
            yell "Service $i killed."
        elif [[ $(check_container_status ${i}-dev) ]]; then
            docker rm -f $(get_container_id ${i}-dev)
            yell "Service $i-dev killed."
        else
            error "Service $i not running."
        fi
        nl
    done
}

function service_start() {
    for SERVICE in $CONTAINERS; do
        if [[ ${#IGNORED_SERVICES[@]} -ne 0 ]]; then
            for IGNORED_SERVICE in "${IGNORED_SERVICES[@]}"; do
                if [[ "$SERVICE" == "${IGNORED_SERVICE}" ]]; then
                    SKIP=1
                    break
                fi
            done
        fi

        if [[ -z "$SKIP" ]]; then
            say "Starting service $SERVICE."
            start_service $SERVICE
            nl
        fi
        unset SKIP
    done
}

function scaffold_destroy_confirmed() {
    kill_all_containers
    $_DC --compatibility -p $PROJECT_NAME $DOCKER_COMPOSE_SCAFFOLD_FILES $DOCKER_COMPOSE_PROFILE down

    # needed because if there are no volumes this might cause the script to exit
    set +eo pipefail
    VOLUMES=$(docker volume ls | tail -n +2 | tr -s " " | cut -d' ' -f2 | grep $PROJECT_NAME)
    set -eo pipefail
    if [[ ${VOLUMES} ]]; then
        _IFS=$IFS
        IFS=$' '
        NAMES=$VOLUMES
        IFS=$_IFS

        for name in $NAMES; do
            say "Destroying volume $name!"
            docker volume rm -f $name
        done
    fi
}

function wait_for_db() {
    say "Waiting for scaffold to start!"
    sleep 3

    while [[ ! $(docker container ls | grep $PROJECT_NAME | grep db | grep Up) ]]; do
        sleep 1
    done

    say "Scaffold is up and running!"
}

function start() {
    if [[ -z "$CLEAN_START" ]]; then
        up_scaffold
        post_up_scaffold
    else
        scaffold_reset
    fi

    service_start
}

SCRIPT_USAGE="${YELLOW}${PROJECT_NAME} CLI ${RESET}\n\nExample usage: ./cli [ start, start-dev, clean-start, clean-start-dev, start-backend, start-backend-dev, clean-start-backend, clean-start-backend-dev, scaffold =>, service =>, build =>, build-and-push ]"

[[ -z "$1" ]] && say "$SCRIPT_USAGE" && exit 1
while test $# -gt 0; do
    case "$1" in
    scaffold)
        scaffold $2 $3 $4 $5
        exit
        ;;
    service)
        service $2 $3 $4 $5
        exit
        ;;
    services)
        service $2 $3 $4 $5
        exit
        ;;
    start)
        start
        exit
        ;;
    start-e2e)
        declare -a IGNORED_SERVICES=("job-generator" "data-sink-worker" "integration-run-worker" "integration-stream-worker")
        start
        exit
        ;;
    start-be)
        declare -a IGNORED_SERVICES=("frontend")
        start
        exit
        ;;
    start-dev)
        # IGNORED_SERVICES=("python-worker" "job-generator" "discord-ws" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker")
        DEV=1
        start
        exit
        ;;
    clean-start)
        CLEAN_START=1
        start
        exit
        ;;
    clean-start-dev)
        # IGNORED_SERVICES=("python-worker" "job-generator" "discord-ws" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker")
        CLEAN_START=1
        DEV=1
        start
        exit
        ;;
    start-backend)
        declare -a IGNORED_SERVICES=("frontend")
        start
        exit
        ;;
    start-backend-dev)
        declare -a IGNORED_SERVICES=("frontend")
        DEV=1
        start
        exit
        ;;
    clean-start-backend)
        declare -a IGNORED_SERVICES=("frontend")
        CLEAN_START=1
        start
        exit
        ;;
    clean-start-backend-dev)
        declare -a IGNORED_SERVICES=("frontend")
        CLEAN_START=1
        DEV=1
        start
        exit
        ;;
    service-restart)
        kill_all_containers
        service_start
        exit
        ;;
    service-restart-be)
        declare -a IGNORED_SERVICES=("frontend")
        kill_all_containers
        service_start
        exit
        ;;
    service-restart-dev)
        DEV=1
        kill_all_containers
        service_start
        exit
        ;;
    service-restart-fe-dev)
        IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "discord-ws" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker")
        DEV=1
        kill_all_containers
        service_start
        exit
        ;;
    clean-start-fe-dev)
        IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "discord-ws" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker")
        CLEAN_START=1
        DEV=1
        start
        exit
        ;;
    deploy-staging)
        select_services
        deploy_staging
        exit
        ;;
    deploy-production)
        select_services
        deploy_production
        exit
        ;;
    reset-selected-services)
        select_services
        reset_selected_services
        exit
        ;;
    reset-selected-services-dev)
        DEV=1
        select_services
        reset_selected_services
        exit
        ;;
    build)
        BUILD=1
        build $2 $3
        exit
        ;;
    build-and-push)
        BUILD=1
        PUSH=1
        build $2 $3
        exit
        ;;
    build-and-push-multiple)
        BUILD=1
        PUSH=1

        COMMIT_HASH=$(git rev-parse --short HEAD)
        TS_VERSION=$(date +%s)
        VERSION="$TS_VERSION.$COMMIT_HASH"

        say "Building images with version tag $VERSION"

        shift
        for IMAGE in "$@"; do
            build $IMAGE $VERSION
        done

        exit
        ;;
    push)
        PUSH=1
        build $2 $3
        exit
        ;;
    migrate-env)
        migrate_env
        exit
        ;;
    db-backup)
        db_backup $2
        exit
        ;;
    db-restore)
        restore_db_backup $2
        exit
        ;;
    lint-backend)
        (cd $CLI_HOME/../backend && pnpm run lint && pnpm run format-check && pnpm run tsc-check)
        exit
        ;;
    lint-services)
        (cd $CLI_HOME/../services/scripts && ./lint_apps.sh && ./lint_libs.sh)
        exit
        ;;
    *)
        error "Invalid command '$1'" && say "$SCRIPT_USAGE"
        exit 1
        ;;
    esac
    shift
done
